package it.unicam.cs.cosy.bioshape.core.behaviours.abstraction;

import it.unicam.cs.cosy.bioshape.core.behaviours.ActionPrefix;
import it.unicam.cs.cosy.bioshape.core.behaviours.Behaviour;
import it.unicam.cs.cosy.bioshape.core.behaviours.Choice;
import it.unicam.cs.cosy.bioshape.core.behaviours.Delay;
import it.unicam.cs.cosy.bioshape.core.behaviours.StrongSplit;
import it.unicam.cs.cosy.bioshape.core.behaviours.WeakSplit;
import it.unicam.cs.cosy.bioshape.core.components.Pair;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

/**
 * Represents a collection of {@code AbstractBasicProcesses}, i.e. abstract
 * S<sup>#</sup>[B<sup>#</sup>] processes linked by bonds. The resulting process
 * is a compound process (i.e. P<sup>#</sup><a, *>Q<sup>#</sup>), saved as an
 * adjacency list. The tuples involved are ordered through a {@code Comparator}
 * so that it is easy to check for equality of two processes.
 * 
 * @author Federico Buti
 */
public class AProcess {

    ArrayList<Pair<ABProcess, ArrayList<AdjTuple>>> bonds;

    /**
     * Generates a new empty process
     */
    public AProcess() {
        bonds = new ArrayList<Pair<ABProcess, ArrayList<AdjTuple>>>();
    }

    /**
     * Generates a new process compound of a basic process only
     * 
     * @param abp
     *            {@code ABProcess} composing the abstract process
     */
    public AProcess(ABProcess abp) {
        bonds = new ArrayList<Pair<ABProcess, ArrayList<AdjTuple>>>();
        bonds.add(new Pair<ABProcess, ArrayList<AdjTuple>>(abp,
                new ArrayList<AdjTuple>()));
    }

    Behaviour getBehaviour(int index) {
        return bonds.get(index).getE1().behaviour.getBehaviour();
    }

    ArrayList<AdjTuple> getAdjs(int index) {
        return bonds.get(index).getE2();
    }

    ABProcess getProcess(int index) {
        return bonds.get(index).getE1();
    }

    /**
     * Checks if the passed {@code AbstractProcess} can create bonds with this
     * {@code AbstractProcess}. If the case, returns the list of new
     * {@code AbstractProcesses} obtained by creating the different bonds
     * possible. <br>
     * The behaviours of the {@code AbstractBasicProcesses} are also incremented
     * to the next behaviour to reflect the creation of the bond and the
     * "consumption" of the channels).
     * 
     * @param process
     *            The process that must be added to the
     * @param results
     *            Data structure containing processes resulting from bindings
     */
    void bind(AProcess process, ArrayList<AProcess> results) {
        for (int i = 0; i < bonds.size(); i++) {
            if (getBehaviour(i).getClass() == ActionPrefix.class)
                for (int j = 0; j < process.bonds.size(); j++)
                    if (process.getBehaviour(j).getClass() == ActionPrefix.class
                            || process.getBehaviour(j).getClass() == Choice.class)
                        checkBinds(i, j, process, results);
            //
            if (getBehaviour(i).getClass() == Choice.class) {
                Choice c = (Choice) getBehaviour(i);
                for (int j = 0; j < c.getComponents().length; j++)
                    if (c.getComponents()[j].getBehaviour().getClass() == ActionPrefix.class)
                        for (int k = 0; k < process.bonds.size(); k++)
                            if (process.getBehaviour(k).getClass() == ActionPrefix.class
                                    || process.getBehaviour(k).getClass() == Choice.class)
                                checkBinds(i, j, k, process, results);
            }
        }
    }

    /**
     * Checks the binding of this process with the passed one in the basic
     * processes passed as indexes. The first one is always an
     * {@code ActionPrefix}. On addition of the tuples for the newly created
     * edges the list of edges is reordered to ease equality check among created
     * processes.
     * 
     * @param i
     *            Index of the basic process of this {@code AProcess}. Always an
     *            {@code ActionPrefix}.
     * @param j
     *            Index of the basic process of second {@code AProcess}.
     * @param process
     *            The process to test this process against for bindings.
     * @param results
     *            Data structure storing the processes resulting from bindings
     */
    private void checkBinds(int i, int j, AProcess process,
            ArrayList<AProcess> results) {
        ActionPrefix a1 = (ActionPrefix) getBehaviour(i);
        if (process.getBehaviour(j).getClass() == ActionPrefix.class) {
            ActionPrefix a2 = (ActionPrefix) process.getBehaviour(j);
            if (a1.isAbstractCompatible(a2)) {
                String bond = a1.getAction().indexOf("'") != -1 ? a2
                        .getAction() : a1.getAction();
                // COPY
                AProcess ap1 = (AProcess) this.clone();
                AProcess ap2 = (AProcess) process.clone();
                // ADVANCE BEHAVIOUR
                ap1.bonds.get(i).getE1().behaviour = ap1.getBehaviour(i)
                        .getNext();
                ap2.bonds.get(j).getE1().behaviour = ap2.getBehaviour(j)
                        .getNext();
                // TUPLE CREATION
                AdjTuple adjT1 = new AdjTuple(bond, ap1.getProcess(i));
                AdjTuple adjT2 = new AdjTuple(bond, ap2.getProcess(j));
                ap1.getAdjs(i).add(adjT2);
                Collections.sort(ap1.getAdjs(i));
                ap2.getAdjs(j).add(adjT1);
                Collections.sort(ap2.getAdjs(j));
                // MERGING AND ADDITION
                ap1.bonds.addAll(ap2.bonds);
                results.add(ap1);
            }
            return;
        }
        Choice c = (Choice) process.getBehaviour(j);
        for (int k = 0; k < c.getComponents().length; k++) {
            if (c.getComponents()[k].getBehaviour().getClass() == ActionPrefix.class) {
                ActionPrefix a2 = (ActionPrefix) c.getComponents()[k];
                if (a1.isAbstractCompatible(a2)) {
                    String bond = a1.getAction().indexOf("'") != -1 ? a2
                            .getAction() : a1.getAction();
                    // COPY
                    AProcess ap1 = (AProcess) this.clone();
                    AProcess ap2 = (AProcess) process.clone();
                    // ADVANCE BEHAVIOUR
                    ap1.bonds.get(i).getE1().behaviour = ap1.getBehaviour(j)
                            .getNext();
                    ap2.bonds.get(j).getE1().behaviour = ((Choice) ap2
                            .getBehaviour(i)).getComponents()[k].getBehaviour()
                            .getNext();
                    // TUPLE CREATION
                    AdjTuple adjT1 = new AdjTuple(bond, ap1.getProcess(i));
                    AdjTuple adjT2 = new AdjTuple(bond, ap2.getProcess(j));
                    ap1.getAdjs(i).add(adjT2);
                    Collections.sort(ap1.getAdjs(i));
                    ap2.getAdjs(j).add(adjT1);
                    Collections.sort(ap2.getAdjs(j));
                    // MERGING AND ADDITION
                    ap1.bonds.addAll(ap2.bonds);
                    results.add(ap1);
                }
            }
        }
    }

    /**
     * Checks the binding of this process with the passed one in the basic
     * processes passed as indexes. The first one is always an {@code Choice}.
     * On addition of the tuples for the newly created edges the list of edges
     * is reordered to ease equality check among created processes.
     * 
     * @param i
     *            Index of the basic process of this {@code AProcess}. Always a
     *            {@code Choice}.
     * @param j
     *            Index of the component of the {@code Choice} to test for
     *            bindings.
     * @param k
     *            Index of the basic process of second {@code AProcess}.
     * @param process
     *            The process to test this process against for bindings.
     * @param results
     *            Data structure storing the processes resulting from bindings
     */
    private void checkBinds(int i, int j, int k, AProcess process,
            ArrayList<AProcess> results) {
        Choice c = (Choice) getBehaviour(i);
        ActionPrefix a1 = (ActionPrefix) c.getComponents()[j].getBehaviour();
        if (process.getBehaviour(k).getClass() == ActionPrefix.class) {
            ActionPrefix a2 = (ActionPrefix) process.getBehaviour(k);
            if (a1.isAbstractCompatible(a2)) {
                String bond = a1.getAction().indexOf("'") != -1 ? a2
                        .getAction() : a1.getAction();
                // COPY
                AProcess ap1 = (AProcess) this.clone();
                AProcess ap2 = (AProcess) process.clone();
                // ADVANCE BEHAVIOUR
                ap1.bonds.get(i).getE1().behaviour = ((Choice) ap1
                        .getBehaviour(i)).getComponents()[j].getBehaviour()
                        .getNext();
                ap2.bonds.get(k).getE1().behaviour = ap2.getBehaviour(k)
                        .getNext();
                // TUPLE CREATION
                AdjTuple adjT1 = new AdjTuple(bond, ap1.getProcess(i));
                AdjTuple adjT2 = new AdjTuple(bond, ap2.getProcess(k));
                ap1.getAdjs(i).add(adjT2);
                Collections.sort(ap1.getAdjs(i));
                ap2.getAdjs(k).add(adjT1);
                Collections.sort(ap2.getAdjs(k));
                // MERGING AND ADDITION
                ap1.bonds.addAll(ap2.bonds);
                results.add(ap1);
                return;
            }
        }
        Choice c2 = (Choice) process.getBehaviour(k);
        for (int l = 0; l < c2.getComponents().length; l++) {
            if (c2.getComponents()[l].getBehaviour().getClass() == ActionPrefix.class) {
                ActionPrefix a2 = (ActionPrefix) c2.getComponents()[l];
                if (a1.isAbstractCompatible(a2)) {
                    String bond = a1.getAction().indexOf("'") != -1 ? a2
                            .getAction() : a1.getAction();
                    // COPY
                    AProcess ap1 = (AProcess) this.clone();
                    AProcess ap2 = (AProcess) process.clone();
                    // ADVANCE BEHAVIOUR
                    ap1.bonds.get(i).getE1().behaviour = ((Choice) ap1
                            .getBehaviour(i)).getComponents()[j].getBehaviour()
                            .getNext();
                    ap2.bonds.get(k).getE1().behaviour = ((Choice) ap2
                            .getBehaviour(k)).getComponents()[l].getBehaviour()
                            .getNext();
                    // TUPLE CREATION
                    AdjTuple adjT1 = new AdjTuple(bond, ap1.getProcess(i));
                    AdjTuple adjT2 = new AdjTuple(bond, ap2.getProcess(k));
                    ap1.getAdjs(i).add(adjT2);
                    Collections.sort(ap1.getAdjs(i));
                    ap2.getAdjs(k).add(adjT1);
                    Collections.sort(ap2.getAdjs(k));
                    // MERGING AND ADDITION
                    ap1.bonds.addAll(ap2.bonds);
                    results.add(ap1);
                }
            }
        }
    }

    /**
     * Checks if the process has bonds to be released. In this method only
     * {@code WeakSplit} operators are checked. A proper method for
     * {@code StrongSplit} is implemented separately. The behaviours of the
     * {@code AbstractBasicProcesses} are also incremented to the next behaviour
     * to reflect the release of the bond represented by a specific weak-split.
     * Finally, the subprocesses obtained from splittings are returned.<br>
     * 
     * @param results
     *            List loaded with the self-bind processes
     */
    ArrayList<AProcess> removeWeakSplitBonds(ArrayList<AProcess> results) {
        for (int i = 0; i < bonds.size(); i++) {
            if (getBehaviour(i).getClass() == WeakSplit.class) {
                WeakSplit ws = (WeakSplit) getBehaviour(i);
                for (AdjTuple adjT : getAdjs(i)) {
                    if (isBondActions(ws.getAction(), adjT.bold))
                        checkWeakSplitBond(getProcess(i), adjT.process, results);
                }
            }
            if (getBehaviour(i).getClass() == Choice.class) {
                Choice c = (Choice) getBehaviour(i);
                for (int j = 0; j < c.getComponents().length; j++) {
                    if (c.getComponents()[j].getBehaviour().getClass() != WeakSplit.class)
                        continue;
                    WeakSplit ws = (WeakSplit) c.getComponents()[j]
                            .getBehaviour();
                    for (AdjTuple adjT : getAdjs(i)) {
                        if (isBondActions(ws.getAction(), adjT.bold))
                            checkWeakSplitBond(getProcess(i), j, adjT.process,
                                    results);
                    }
                }
            }
        }
        return results;
    }

    /**
     * Checks if the passed processes can release a bond between them and create
     * effectively the new, split, processes. The first process contains
     * <i>always</i> a {@code WeakSplit} whereas the second can contain a
     * {@code WeakSplit} or a {@code Choice}. Hence, the method must check the
     * second process and execute the correct operation accordingly.
     * 
     * @param proc1
     *            First process to check, it is a {@code WeakSplit}.
     * @param proc2
     *            Second process to check, it is a {@code WeakSplit} or a
     *            {@code Choice}.
     * @param results
     *            Data structure storing the processes resulting from splittings
     */
    private void checkWeakSplitBond(ABProcess proc1, ABProcess proc2,
            ArrayList<AProcess> results) {
        WeakSplit w1 = (WeakSplit) proc1.behaviour.getBehaviour();
        WeakSplit w2;
        // weaksplit - weaksplit
        if (proc2.behaviour.getBehaviour().getClass() == WeakSplit.class) {
            w2 = (WeakSplit) proc2.behaviour.getBehaviour();
            if (w1.isAbstractCompatible(w2)) {
                // COPY
                AProcess ap = (AProcess) this.clone();
                int i1 = getIndex(proc1);
                int i2 = getIndex(proc2);
                // ADVANCE BEHAVIOUR
                ap.bonds.get(i1).getE1().behaviour = ap.getBehaviour(i1)
                        .getNext();
                ap.bonds.get(i2).getE1().behaviour = ap.getBehaviour(i2)
                        .getNext();
                ap.removeBond(i1, i2, w1.getAction());
                // FIND SUBGRAPHS
                ap.findSubGraphs(results, i1, i2);
            }
            return;
        }
        // weaksplit - choice
        if (proc2.behaviour.getBehaviour().getClass() == Choice.class) {
            Choice c = (Choice) proc2.behaviour.getBehaviour();
            for (int j = 0; j < c.getComponents().length; j++) {
                if (c.getComponents()[j].getBehaviour().getClass() != WeakSplit.class)
                    continue;
                w2 = (WeakSplit) c.getComponents()[j].getBehaviour();
                if (w1.isAbstractCompatible(w2)) {
                    // COPY
                    AProcess ap = (AProcess) this.clone();
                    int i1 = getIndex(proc1);
                    int i2 = getIndex(proc2);
                    // ADVANCE BEHAVIOUR
                    ap.bonds.get(i1).getE1().behaviour = ap.getBehaviour(i1)
                            .getNext();
                    ap.bonds.get(i2).getE1().behaviour = ((Choice) ap
                            .getBehaviour(i2)).getComponents()[j].getNext();
                    ap.removeBond(i1, i2, w1.getAction());
                    // FIND SUBGRAPHS
                    ap.findSubGraphs(results, i1, i2);
                }
            }
        }
    }

    /**
     * Checks if the passed processes can release a bond between them and create
     * effectively the new, split, processes. The first process contains
     * <i>always</i> a {@code Choice} whereas the second can contain a
     * {@code WeakSplit} or a {@code Choice}. Hence, the method must check the
     * second process and execute the correct operation accordingly.
     * 
     * @param proc1
     *            First process to check, it is a {@code Choice}.
     * @param index
     *            Index of the {@code Choice} component to check for splitting
     * @param proc2
     *            Second process to check, it is a {@code WeakSplit} or a
     *            {@code Choice}.
     * @param results
     *            Data structure storing the processes resulting from splittings
     */
    private void checkWeakSplitBond(ABProcess proc1, int index,
            ABProcess proc2, ArrayList<AProcess> results) {
        Choice c = (Choice) proc1.behaviour.getBehaviour();
        WeakSplit w1 = (WeakSplit) c.getComponents()[index];
        WeakSplit w2;
        // choice - weaksplit
        if (proc2.behaviour.getBehaviour().getClass() == WeakSplit.class) {
            w2 = (WeakSplit) proc2.behaviour.getBehaviour();
            if (w1.isAbstractCompatible(w2)) {
                // COPY
                AProcess ap = (AProcess) this.clone();
                int i1 = getIndex(proc1);
                int i2 = getIndex(proc2);
                // ADVANCE BEHAVIOUR
                ap.bonds.get(i1).getE1().behaviour = ((Choice) ap
                        .getBehaviour(i1)).getComponents()[index].getNext();
                ap.bonds.get(i2).getE1().behaviour = ap.getBehaviour(i2)
                        .getNext();
                ap.removeBond(i1, i2, w1.getAction());
                // FIND SUBGRAPHS
                ap.findSubGraphs(results, i1, i2);
            }
            return;
        }
        // choice - choice
        if (proc2.behaviour.getBehaviour().getClass() == Choice.class) {
            Choice c2 = (Choice) proc2.behaviour.getBehaviour();
            for (int j = 0; j < c2.getComponents().length; j++) {
                if (c2.getComponents()[j].getBehaviour().getClass() != WeakSplit.class)
                    continue;
                w2 = (WeakSplit) c2.getComponents()[j].getBehaviour();
                if (w1.isAbstractCompatible(w2)) {
                    // COPY
                    AProcess ap = (AProcess) this.clone();
                    int i1 = getIndex(proc1);
                    int i2 = getIndex(proc2);
                    // ADVANCE BEHAVIOUR
                    ap.bonds.get(i1).getE1().behaviour = ((Choice) ap
                            .getBehaviour(i1)).getComponents()[index].getNext();
                    ap.bonds.get(i2).getE1().behaviour = ((Choice) ap
                            .getBehaviour(i2)).getComponents()[j].getNext();
                    ap.removeBond(i1, i2, w1.getAction());
                    // FIND SUBGRAPHS
                    ap.findSubGraphs(results, i1, i2);
                }
            }
        }
    }

    /**
     * Extracts the subgraphs (i.e. the sub-processes) obtained from a
     * splitting. The parameter are variable since the strong-split can involve
     * <i>several</i> splittings at a time. Weak-split is instead always a
     * single split between two processes.<br>
     * Given the split processes with a greedy approach we build the sub-graphs.
     * 
     * @param results
     *            The data structure storing the resulting split processes.
     * @param indices
     *            Indices of the nodes in which the split occurred. We start the
     *            sub-process construction from them.
     */
    private void findSubGraphs(ArrayList<AProcess> results, int... indices) {
        AProcess temp = new AProcess();
        Stack<Integer> stack = new Stack<Integer>();
        for (int index : indices) {
            // empty temp variable
            temp.bonds.clear();
            stack.clear();
            stack.add(new Integer(index));
            // iteration on nodes
            while (!stack.isEmpty()) {
                int ind = stack.pop().intValue();
                if (temp.bonds.contains(bonds.get(ind)))
                    continue;
                //
                temp.bonds.add(bonds.get(ind));
                for (AdjTuple adj : getAdjs(ind)) {
                    stack.push(new Integer(getIndex(adj.process)));
                }
            }
            // clone the created sub-graph
            results.add((AProcess) temp.clone());
        }
    }

    /**
     * Removes a bond linking two {@code ABProcess} objects identified by their
     * indices.
     * 
     * @param i1
     *            Index of the first {@code ABProcess}
     * @param i2
     *            Index of the second {@code ABProcess}.
     * @param action
     *            Name of the bond between the two processes.
     */
    private void removeBond(int i1, int i2, String action) {
        if (action.indexOf("'") != -1)
            action = action.substring(1);
        for (int i = 0; i < getAdjs(i1).size(); i++) {
            if (getAdjs(i1).get(i).process.equals(getProcess(i2))
                    && getAdjs(i1).get(i).bold.contains(action)) {
                getAdjs(i1).remove(i);
                break;
            }
        }
        for (int i = 0; i < getAdjs(i2).size(); i++) {
            if (getAdjs(i2).get(i).process.equals(getProcess(i1))
                    && getAdjs(i2).get(i).bold.contains(action)) {
                getAdjs(i2).remove(i);
                break;
            }
        }
    }

    /**
     * 
     * @param ws
     *            The {@code ABProcess} to search for.
     * 
     * @return The index in the current {@code AProcess} of the passed
     *         {@code ABProcess} object or -1 if the abstract basic process does
     *         not belong to this abstract process
     */
    private int getIndex(ABProcess ws) {
        for (int i = 0; i < bonds.size(); i++)
            if (bonds.get(i).getE1().equals(ws))
                return i;
        return -1;
    }

    /**
     * Checks if the process has bonds to be released. In this method only
     * {@code StrongSplit} operators are checked. A proper method for
     * {@code WeakSplit} is implemented separately. The behaviours of the
     * {@code AbstractBasicProcesses} are also incremented to the next behaviour
     * to reflect the release of the bond represented by a specific
     * strong-split. Finally, the subprocesses obtained from splittings are
     * returned.<br>
     * Differently from {@code WeakSplit} the strong operator can contain a
     * <i>set</i> of bonds. Hence, this method must traverse the process to find
     * the <i>subset</i> of {@code StrongSplit} which can correctly release
     * their bonds.
     * 
     * @param results
     *            The data structure storing the resulting split processes.
     */
    void removeStrongSplitBonds(ArrayList<AProcess> results) {
        ArrayList<Object[]> nodes = new ArrayList<Object[]>();
        for (int i = 0; i < bonds.size(); i++) {
            nodes.clear();
            Class<? extends Behaviour> clazz = getBehaviour(i).getClass();
            if (clazz == StrongSplit.class)
                if (checkStrongSplittings(i, -1, null, nodes))
                    generateStrongSplitProcesses(nodes, results);
            if (clazz == Choice.class) {
                Choice c = (Choice) getBehaviour(i);
                for (int j = 0; j < c.getComponents().length; j++)
                    if (c.getComponents()[j].getBehaviour().getClass() == StrongSplit.class)
                        if (checkStrongSplittings(i, j, null, nodes))
                            generateStrongSplitProcesses(nodes, results);
            }
        }
    }

    /**
     * Executed each time the traversing method for strong-splits complete the
     * creation of a group of splits. Creates a copy of the current process and
     * removes the bonds of the strong splits. Update the behaviour accordingly
     * and calls {{@link #findSubGraphs(ArrayList, int...)} on the processes
     * involved in the strong splits to generate the split processes.
     * 
     * @param nodes
     *            result loaded with the result of splittings.
     * @param results
     *            The data structure storing the resulting split processes.
     */
    @SuppressWarnings("boxing")
    private void generateStrongSplitProcesses(ArrayList<Object[]> nodes,
            ArrayList<AProcess> results) {
        Set<Integer> indices = new HashSet<Integer>();
        // COPY
        AProcess ap = (AProcess) this.clone();
        // ADVANCE BEHAVIOUR / REMOVE BONDS
        for (Object[] edge : nodes) {
            if (((Integer) edge[1]).intValue() == -1)
                ap.bonds.get(((Integer) edge[0]).intValue()).getE1().behaviour = ap
                        .getBehaviour(((Integer) edge[0]).intValue()).getNext();
            else
                ap.bonds.get(((Integer) edge[0]).intValue()).getE1().behaviour = ((Choice) ap
                        .getBehaviour(((Integer) edge[0]).intValue()))
                        .getComponents()[((Integer) edge[1]).intValue()]
                        .getNext();
            if (((Integer) edge[3]).intValue() == -1)
                ap.bonds.get(((Integer) edge[2]).intValue()).getE1().behaviour = ap
                        .getBehaviour(((Integer) edge[2]).intValue()).getNext();
            else
                ap.bonds.get(((Integer) edge[2]).intValue()).getE1().behaviour = ((Choice) ap
                        .getBehaviour(((Integer) edge[2]).intValue()))
                        .getComponents()[((Integer) edge[3]).intValue()]
                        .getNext();
            //
            ap.removeBond(((Integer) edge[0]).intValue(),
                    ((Integer) edge[2]).intValue(), (String) edge[5]);
            indices.add((Integer) edge[0]);
            indices.add((Integer) edge[2]);
        }
        // Collect the split nodes and pass them to the method for finding
        // subgraphs
        int[] indexes = new int[indices.size()];
        int i = 0;
        for (Integer integer : indices)
            indexes[i++] = integer.intValue();
        ap.findSubGraphs(results, indexes);
    }

    /**
     * Starting from the given index {@code ABProcess} containing a
     * {@code StrongSplit} behaviour, the method traverse the bonds compatible
     * with the set of bonds names to release, trying to build a "release set",
     * i.e. a set of strong-splits which releases the bonds names of all the
     * involved strong split operators. This is in accordance to the concept of
     * "able to complete a reaction" defined both in the concrete and abstract
     * semantics.<br>
     * Note that in the abstract setting a process is <i>always</i> able to
     * complete a reaction if all the involved strong splits are enabled,
     * regardless of timesteps (which are discarded in the abstract setting).
     * 
     * @param i
     *            Index of the process containing the {@code StrongSplit} or the
     *            {@code Choice} behaviour.
     * @param j
     *            Set to a positive value if the passed process is a
     *            {@code Choice}, -1 otherwise.
     * @param bond
     *            Name of the bond from which the method has been called. Passed
     *            to avoid searching for that bond.
     * @param data
     *            Contains info about the nodes (and bonds) belonging to a
     *            {@code StrongSplit} defined over this process.
     */
    @SuppressWarnings("boxing")
    private boolean checkStrongSplittings(int i, int j, String bond,
            ArrayList<Object[]> data) {
        ArrayList<Object[]> temp = new ArrayList<Object[]>();
        // Extracts the StrongSplit, from a choice if necessary
        StrongSplit ss = j == -1 ? (StrongSplit) getBehaviour(i)
                : (StrongSplit) ((Choice) getBehaviour(i)).getComponents()[j];
        // iteration over the bonds to be released by the choice
        String action;
        ex: for (Pair<String, String> ch : ss.getChannels()) {
            action = ch.getE1();
            if (isBondActions(action, bond)) // already satisfied
                continue;
            for (AdjTuple adjT : getAdjs(i)) {
                if (adjT.process.behaviour.getBehaviour().getClass() == StrongSplit.class) {
                    if (true/* controllo soddisfatto per specifica azione */) {
                        if (checkStrongSplittings(getIndex(adjT.process), -1,
                                action, data)) {
                            temp.add(new Object[] { i, j,
                                    getIndex(adjT.process), -1, adjT.bold });
                            continue ex;
                        }
                    }
                }
                if (adjT.process.behaviour.getBehaviour().getClass() == Choice.class) {
                    Choice c = (Choice) adjT.process.behaviour.getBehaviour();
                    for (int k = 0; k < c.getComponents().length; k++) {
                        if (c.getComponents()[k].getBehaviour().getClass() != StrongSplit.class)
                            continue;
                        StrongSplit ss2 = (StrongSplit) ((Choice) getBehaviour(i))
                                .getComponents()[k];
                        if (true/* controllo soddisfatto per specifica azione */) {
                            if (checkStrongSplittings(getIndex(adjT.process),
                                    -1, action, data)) {
                                temp.add(new Object[] { i, j,
                                        getIndex(adjT.process), k, adjT.bold });
                                continue ex;
                            }
                        }
                    }

                }
            }
            // if no compatible split if found among the available
            return false;
        }
        // if everything was ok we can reach this code: addition
        data.addAll(temp);
        return true;
    }

    @Override
    public Object clone() {
        AProcess ap = new AProcess();
        // COPY PROCESSES
        for (Pair<ABProcess, ArrayList<AdjTuple>> pair : bonds) {
            ap.bonds.add(new Pair<ABProcess, ArrayList<AdjTuple>>(
                    new ABProcess(pair.getE1().name, pair.getE1().behaviour),
                    new ArrayList<AdjTuple>()));
        }
        // COPY LISTS
        for (int i = 0; i < bonds.size(); i++) {
            for (AdjTuple adjT : getAdjs(i)) {
                int j = getIndex(adjT.process);
                ap.getAdjs(i).add(new AdjTuple(adjT.bold, ap.getProcess(j)));
            }
        }
        return ap;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null)
            return false;
        if (getClass() != o.getClass())
            return false;
        AProcess ap = (AProcess) o;
        //
        if (bonds.size() != ap.bonds.size())
            return false;
        //
        for (int i = 0; i < bonds.size(); i++) {
            Pair<ABProcess, ArrayList<AdjTuple>> procA = bonds.get(i);
            Pair<ABProcess, ArrayList<AdjTuple>> procB = ap.bonds.get(i);
            //
            if (!procA.getE1().equals(procB.getE1()))
                return false;
            //
            if (procA.getE2().size() != procB.getE2().size())
                return false;
            //
            for (int j = 0; j < procA.getE2().size(); j++) {
                if (!procA.getE2().get(j).equals(procB.getE2().get(j)))
                    return false;
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 92821;
        int result = 1;
        result = prime * result + bonds.hashCode();
        return result;
    }

    // TODO: can be used to substituted default bonds hashCode function
    private int bondsHashCode() {
        int hashCode = 1;
        for (Pair<ABProcess, ArrayList<AdjTuple>> pair : bonds) {
            hashCode = 31 * hashCode + (pair.getE1().hashCode());
            hashCode = 31 * hashCode + (pair.getE2().hashCode());
        }
        return hashCode;
    }

    /**
     * Iterates on the adjacency list checking that the next process is a
     * {@code Delay}. Any {@code Delay} is consumes so that the subsequent non-
     * {@code Delay} process is considered in the next iteration.
     */
    void skipDelays() {
        for (Pair<ABProcess, ArrayList<AdjTuple>> proc : bonds) {
            while (proc.getE1().behaviour.getBehaviour() instanceof Delay) {
                proc.getE1().behaviour = proc.getE1().behaviour.getNext();
            }
            //
            if (proc.getE1().behaviour.getBehaviour() instanceof Choice) {
                Choice c = (Choice) proc.getE1().behaviour.getBehaviour();
                for (Behaviour be : c.getComponents())
                    while (be.getBehaviour() instanceof Delay) {
                        be = be.getNext();
                    }
            }
        }
    }

    /**
     * Builds the final Shape Calculus process string associated to this
     * process. It iterates over the component basic processes, adding to the
     * final string each process if it has not been added before by another one
     * (when a bond has been print).
     */
    @Override
    public String toString() {
        boolean[] checked = new boolean[bonds.size()];
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bonds.size(); i++) {
            if (!checked[i]) {
                sb.append(bonds.get(i).getE1().getLabel());
                checked[i] = true;
            }
            for (AdjTuple adj : bonds.get(i).getE2()) {
                int index = getIndex(adj.process);
                if (!checked[(index)]) {
                    sb.append("  \u3008" + adj.bold + ",\u2299\u3009  "
                            + adj.process.getLabel());
                    checked[index] = true;
                }
            }
        }
        return sb.toString();
    }

    /**
     * Checks if the bond name is compatible with the passed action, i.e. if the
     * action "a" corresponds to the bond "b". Since the bond is <i>always</i> a
     * name and <i>never</i> a coname the passed action is checked both as a
     * whole or substringing the first character.
     * 
     * @param a
     *            The action to check.
     * @param b
     *            The bond action to check action against.
     * @return {@code true} if the action and the bond action are compatible,
     *         {@code false} otherwise
     */
    private boolean isBondActions(String a, String b) {
        if (a.substring(1).equals(b))
            return true;
        if (a.equals(b))
            return true;
        return false;
    }
}